Skip to content

feat(core): intelligent tool parallelism with Kind-based batching and shell read-only detection#2864

Open
wenshao wants to merge 8 commits intoQwenLM:mainfrom
wenshao:feat/tool-parallelism
Open

feat(core): intelligent tool parallelism with Kind-based batching and shell read-only detection#2864
wenshao wants to merge 8 commits intoQwenLM:mainfrom
wenshao:feat/tool-parallelism

Conversation

@wenshao
Copy link
Copy Markdown
Collaborator

@wenshao wenshao commented Apr 3, 2026

What this PR does

When the model returns multiple tool calls in one response, they used to execute one by one — even if they're all read-only. This PR makes read-only tools run in parallel.

Before vs After

Model returns: [Read₁, Read₂, Grep, Bash("git log"), Read₃]

Before:  Read₁ → Read₂ → Grep → Bash → Read₃     (5 sequential steps)
After:   [Read₁ + Read₂ + Grep + Bash + Read₃]     (1 parallel step)

With mixed safe/unsafe tools, ordering is preserved via consecutive batching — consecutive safe tools are grouped into one parallel batch, but an unsafe tool breaks the batch:

Model returns: [Read₁, Read₂, Edit, Read₃]

Batch 1: [Read₁ + Read₂]  ← parallel (both safe, consecutive)
Batch 2: [Edit]            ← sequential (unsafe, breaks the batch)
Batch 3: [Read₃]           ← sequential (safe, but alone after the break)

3 steps instead of 4. Read₃ can't join Batch 1 because Edit sits between them.

What's safe to parallelize

Tools Why
Parallel Read, Grep, Glob, WebFetch, WebSearch, ListDirectory Kind-based: Read/Search/Fetch have no side effects
Parallel Shell read-only commands (git log, git status, cat, ls, wc, diff, ...) Checked by existing isShellCommandReadOnly() — a whitelist of ~30 commands with git subcommand validation. Unknown commands → sequential (fail-closed)
Parallel Agent Spawns independent subprocesses, no shared state
Sequential Edit, WriteFile File mutations must preserve ordering
Sequential Shell mutating (npm install, rm, mkdir, ...) Not on the read-only whitelist
Sequential TodoWrite, SaveMemory Kind.Think but write to disk
Sequential Everything else Fail-closed: unknown tools default to sequential

Behavior change: Agent tools

Previously, Agent tools were unconditionally pulled into a separate concurrent group (ran in parallel regardless of position). Now they follow the same consecutive batching rules as other safe tools. [Edit, Agent] previously ran concurrently; now Agent waits for Edit to finish. This is safer — it preserves the model's intended ordering.

Comparison with Claude Code

Capability Claude Code Qwen Code (after this PR)
Read-only tool parallelism Yes Yes
Consecutive batch grouping Yes Yes
Shell read-only detection regex + shell-quote + per-flag validation regex + shell-quote + command whitelist (same approach, comparable coverage)
Streaming tool execution Yes No (minimal benefit on Gemini API)

Core parallel execution capability is now on par with Claude Code.

Note: Qwen Code also has a tree-sitter-bash AST parser (isShellCommandReadOnlyAST) used for permission decisions. The concurrency check uses the synchronous regex-based checker for performance (partitioning must be synchronous). Both checkers share the same command whitelist.

Changes

File Change
tools.ts Add CONCURRENCY_SAFE_KINDS set (Read, Search, Fetch)
coreToolScheduler.ts isConcurrencySafe(), partitionToolCalls(), runConcurrently() with shell read-only detection and configurable concurrency cap (QWEN_CODE_MAX_TOOL_CONCURRENCY, default 10)
mock-tool.ts Support custom kind option for testing
coreToolScheduler.test.ts Tests for all-safe parallel, mixed batching, shell read-only concurrency, env isolation

Test plan

  • npx vitest run packages/core/src/core/coreToolScheduler.test.ts — 55 tests pass
  • npx tsc --noEmit -p packages/core/tsconfig.json — type check passes
  • Manual: model returns multiple read_file calls → parallel execution confirmed

🤖 Generated with Claude Code

…ching

Replace the hard-coded Agent-vs-others split with consecutive batching
based on tool Kind. Read-only tools (Read, Search, Fetch, Think) now
execute in parallel; mutating tools (Edit, Execute) run sequentially.

- Add CONCURRENCY_SAFE_KINDS set to tools.ts
- Add partitionToolCalls() for consecutive batch grouping
- Add isConcurrencySafe() helper (Agent name + Kind check)
- Add runConcurrently() with configurable concurrency cap
  (QWEN_CODE_MAX_TOOL_CONCURRENCY env var, default 10)
- Update MockTool to support custom Kind for testing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

📋 Review Summary

This PR implements intelligent tool parallelism based on tool Kind, replacing the hard-coded Agent-only concurrent execution model. The implementation is well-structured, with clear separation of concerns and comprehensive test coverage. The changes enable read-only tools (Read, Search, Fetch, Think) to execute in parallel while preserving ordering semantics for mutating tools (Edit, Execute).

🔍 General Feedback

  • Clean architecture: The helper functions (isConcurrencySafe, partitionToolCalls, runConcurrently) are well-isolated and follow single-responsibility principles.
  • Good documentation: JSDoc comments clearly explain the batching behavior with concrete examples.
  • Thoughtful design: The consecutive batching approach preserves implicit ordering that models may rely on while maximizing parallelism opportunities.
  • Configurable concurrency: The QWEN_CODE_MAX_TOOL_CONCURRENCY environment variable provides operational flexibility.
  • Test coverage: The updated test properly validates that all concurrency-safe tools run in parallel rather than just Agent tools.

🎯 Specific Feedback

🟡 High

  • File: packages/core/src/core/coreToolScheduler.ts:344-349 - The isConcurrencySafe function checks ToolNames.AGENT by name but other tools by kind. This creates an inconsistency in how concurrency safety is determined. Consider whether Agent should also have a Kind.Agent value added to CONCURRENCY_SAFE_KINDS for consistency, or document why Agent is treated as a special case.

  • File: packages/core/src/core/coreToolScheduler.ts:358-369 - The partitionToolCalls logic groups consecutive safe tools but creates a sequential batch for each unsafe tool. This means [Read, Edit, Read] becomes [[Read](parallel), [Edit](seq), [Read](seq)] - the second Read could potentially run in parallel with the first if they're both safe. However, the current design intentionally preserves ordering, which is correct but worth documenting more explicitly that this is a deliberate ordering guarantee, not a limitation.

🟢 Medium

  • File: packages/core/src/core/coreToolScheduler.ts:1353-1371 - The runConcurrently function uses a Set to track executing promises and removes them via .then() callback. However, if a promise rejects before completion, the .then() callback may not execute properly. Consider using .finally() instead to ensure cleanup:

    const p = this.executeSingleToolCall(call, signal).finally(() => {
      executing.delete(p);
    });
  • File: packages/core/src/tools/tools.ts:740-746 - The CONCURRENCY_SAFE_KINDS set is exported but only used internally by coreToolScheduler.ts. Consider whether this should be exported or kept internal to reduce API surface. If exported, consider adding a comment explaining the safety guarantees (no side effects, no shared mutable state).

  • File: packages/core/src/core/coreToolScheduler.ts:344 - The comment mentions "no side effects, no shared mutable state" but the isConcurrencySafe function doesn't verify these properties - it relies on the Kind classification. Consider adding a stronger comment that tool implementers must ensure their Kind-appropriate tools actually satisfy these guarantees.

🔵 Low

  • File: packages/core/src/core/coreToolScheduler.test.ts:3147 - The test timeout was increased from 20ms to 50ms for the read tool. Consider using a more descriptive constant or configuration value to make the test intent clearer (e.g., const TOOL_EXECUTION_DELAY = 50).

  • File: packages/core/src/core/coreToolScheduler.ts:334 - The decorative separator line (// ─── Tool Concurrency Helpers ────────────────────────────────) uses Unicode characters that may not render consistently across all editors. Consider using standard ASCII separators for better compatibility.

  • File: packages/core/src/test-utils/mock-tool.ts:27 - The kind?: Kind option is added but there's no validation that the provided kind matches the tool's actual behavior. Consider adding a comment or validation in test utilities to ensure test authors don't misclassify mock tools.

✅ Highlights

  • Excellent test update: The test at line 3122 properly validates that ALL concurrency-safe tools (not just Agent) run in parallel, with clear assertions checking that all starts happen before any ends.
  • Smart batching strategy: The partitionToolCalls function elegantly handles the complex scenario of interleaved safe/unsafe tools while preserving ordering guarantees.
  • Environment variable configuration: Adding QWEN_CODE_MAX_TOOL_CONCURRENCY shows operational maturity and allows tuning for different environments.
  • Minimal changes: The implementation achieves significant functionality with focused changes to only 4 files, demonstrating good modular design.
  • Type safety: The use of ReadonlySet<Kind> and proper TypeScript typing ensures the concurrency-safe kinds list is immutable and type-checked.

Shell commands detected as read-only (e.g., git log, cat, ls) now run
concurrently with other safe tools instead of breaking parallel batches.

Uses the existing isShellCommandReadOnly() checker (synchronous,
fail-closed). Commands that can't be verified as read-only remain
sequential.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the core tool scheduling logic to reduce end-to-end latency by executing consecutive “concurrency-safe” tool calls in parallel (batched by tool Kind), instead of only running Agent tools concurrently.

Changes:

  • Introduces a CONCURRENCY_SAFE_KINDS allowlist to classify tool kinds as parallel-safe.
  • Adds batching + capped parallel execution to CoreToolScheduler via partitionToolCalls() and runConcurrently().
  • Updates test utilities and scheduler tests to support/verify kind-driven parallelism.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
packages/core/src/tools/tools.ts Adds a concurrency-safe Kind set used to determine which tool calls may run in parallel.
packages/core/src/core/coreToolScheduler.ts Implements Kind-based batching, safety checks, and concurrency-capped parallel execution.
packages/core/src/test-utils/mock-tool.ts Allows tests to assign a custom kind to mock tools.
packages/core/src/core/coreToolScheduler.test.ts Updates the concurrency test to assert parallel execution for concurrency-safe tools.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@wenshao wenshao changed the title feat(core): implement intelligent tool parallelism via Kind-based batching feat(core): intelligent tool parallelism with Kind-based batching and shell read-only detection Apr 3, 2026
- Remove Kind.Think from CONCURRENCY_SAFE_KINDS (save_memory and
  todo_write write to disk)
- Use .finally() instead of .then() in runConcurrently for cleanup
- Validate maxConcurrency (clamp to >= 1, default 10)
- Add comment explaining why sync checker is used over async AST
- Add test for mixed safe/unsafe tool batch partitioning

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

… calls

Let all calls go through executeSingleToolCall which handles abort
internally, ensuring every tool reaches a terminal state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Update batching comment to clarify Execute conditional safety
- Rename describe block to "Concurrent tool execution"
- Add test for shell read-only concurrency (git log + ls parallel,
  npm install sequential)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Prevents false-positive test passes when expected log entries are
missing (indexOf returns -1 which is always < any positive index).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

wenshao added a commit to wenshao/codeagents that referenced this pull request Apr 3, 2026
Major additions to qwen-code-improvement-report.md:

1. New P0 item: Mid-Turn Queue Drain (PR QwenLM/qwen-code#2854 open)
2. Upgraded P2→P1: Tool parallelism with Kind-based batching
   (PR QwenLM/qwen-code#2864 open)
3. New P1 items: startup optimization, CLAUDE.md conditional rules
4. New P2 items: shell security, MDM enterprise, API token counting
5. Added Fork Subagent deep-dive cross-reference link
6. Expanded summary table from 10→16 dimensions with "进展" column
   tracking Qwen Code PR status
7. Fixed forkSubagent.ts line count 211→210

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
wenshao added a commit to wenshao/codeagents that referenced this pull request Apr 3, 2026
…rt (#34)

* docs: add English terms to technical concepts, fix leaked source reference and relative paths

- Remove 'leaked' reference in qwen-code-improvement-report.md
- Fix 14 relative paths from '../claude-code-leaked/' to 'claude-code/'
- Add English annotations to technical terms across 10 comparison docs
  (Speculation, Context Compression, Subagent, Telemetry, etc.)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: convert source code references to clickable local links in qwen-code-improvement-report.md

- Add local source paths section (claude-code-leaked + qwen-code)
- Convert 14 Claude Code source refs to clickable links
- Convert 7 Qwen Code source refs to clickable links

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: fix qwen-code-improvement-report.md local links

- Replace external source code links with internal article links
- Convert 21 external file links to internal documentation links
- Add related article references for all 5 Top 5 improvement sections
- Use '源码:' format for source code references instead of clickable links

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: correct improvement report accuracy issues

1. reactiveCompact.ts does NOT exist — changed to apiMicrocompact.ts
2. Qwen Code speculation is complete in v0.15.0 (563 lines + overlayFs +
   speculationToolGate), only default-disabled — updated description,
   matrix row, summary table, and suggestions
3. Claude Code speculation.ts: 992 → 991 lines

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: replace 'leaked 源码' with '源码分析' in startup-optimization disclaimer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address Copilot review — rename claude-code-leaked path reference

Changed `../claude-code-leaked/` → `../claude-code/`(源码快照)in the
source path hint to avoid inconsistency with "源码分析" description.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: enhance improvement report with deep-dive findings and PR status

Major additions to qwen-code-improvement-report.md:

1. New P0 item: Mid-Turn Queue Drain (PR QwenLM/qwen-code#2854 open)
2. Upgraded P2→P1: Tool parallelism with Kind-based batching
   (PR QwenLM/qwen-code#2864 open)
3. New P1 items: startup optimization, CLAUDE.md conditional rules
4. New P2 items: shell security, MDM enterprise, API token counting
5. Added Fork Subagent deep-dive cross-reference link
6. Expanded summary table from 10→16 dimensions with "进展" column
   tracking Qwen Code PR status
7. Fixed forkSubagent.ts line count 211→210

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: simplify improvement matrix table to 5 columns, add deep-dive links

- Reduced matrix from 7 columns to 5 (优先级/改进点/现状/难度/进展)
  to eliminate GitHub horizontal scrollbar
- Added section 五 with 12 deep-dive cross-reference links
- Tracked Qwen Code PR status: #2854 (mid-turn drain), #2864 (parallelism),
  #2525 (speculation, merged)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: correct line counts and anchor link in improvement report

- compact.ts: 1706→1705
- AgentTool.tsx: 1398→1397
- runAgent.ts: 974→973
- autoDream.ts: 325→324
- Fix anchor: #相关-deep-dive-文章 → #五相关-deep-dive-文章

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wenshao wenshao added the DDAR DataWorks Data Agent Ready label Apr 4, 2026
@tanzhenxin tanzhenxin added the type/feature-request New feature or enhancement request label Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DDAR DataWorks Data Agent Ready type/feature-request New feature or enhancement request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants